<?php

namespace app\component;

use support\Response;
use support\XunshanContainer;

class User
{
    public $identityClass = '';

    private $_identity = false;

    public $idParam = '__id';

    private $identityCookie = '_identity';

    public function __construct()
    {
        $this->identityClass = params('identityClass', '');
        if ($this->identityClass == '') {
            throw new \Exception('params.php identityClass is not defined!');
        }
    }

    /**
     * Returns the identity object associated with the currently logged-in user.
     * When [[enableSession]] is true, this method may attempt to read the user's authentication data
     * stored in session and reconstruct the corresponding identity object, if it has not done so before.
     *
     * @param bool $autoRenew whether to automatically renew authentication status if it has not been done so before.
     *                        This is only useful when [[enableSession]] is true.
     * @return IdentityInterface|null|bool the identity object associated with the currently logged-in user.
     *                        `null` is returned if the user is not logged in (not authenticated).
     * @see logout()
     * @see login()
     */
    public function getIdentity($autoRenew = true)
    {
        if ($this->_identity === false) {
            if ($autoRenew) {
                try {
                    $this->_identity = null;
                    $this->renewAuthStatus();
                } catch (\Exception $e) {
                    $this->_identity = false;
                } catch (\Throwable $e) {
                    $this->_identity = false;
                }
            } else {
                return null;
            }
        }

        return $this->_identity;
    }

    /**
     * Sets the user identity object.
     *
     * Note that this method does not deal with session or cookie. You should usually use [[switchIdentity()]]
     * to change the identity of the current user.
     *
     * @param IdentityInterface|null $identity the identity object associated with the currently logged user.
     * If null, it means the current user will be a guest without any associated identity.
     */
    public function setIdentity($identity)
    {
        if ($identity instanceof IdentityInterface) {
            $this->_identity = $identity;
        } elseif ($identity === null) {
            $this->_identity = null;
        }
    }

    /**
     * @param IdentityInterface $identity
     * @param int               $duration
     */
    public function switchIdentity($identity, $duration = 0)
    {
        $this->setIdentity($identity);

        if ($identity === null) {
            $this->removeIdentityCookie();
        }

        $session = request()->session();
        $session->delete($this->idParam);

        if ($identity) {
            $session->set($this->idParam, $identity->getId());
            $this->sendIdentityCookie($identity, $duration);
        }
    }

    protected function removeIdentityCookie()
    {
        /** @var Response $response */
        XunshanContainer::response()->getCookies()->remove($this->identityCookie);
    }

    protected function sendIdentityCookie($identity, $duration)
    {
        $cookie = new Cookie();
        $cookie->name = $this->identityCookie;
        /** @var IdentityInterface $identity */
        $cookie->value = json_encode([
            $identity->getId(),
            $identity->getAuthKey(),
            $duration,
        ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        $cookie->expire = time() + $duration;
        /** @var Response $response */
        XunshanContainer::response()->getCookies()->add($cookie);
    }

    /**
     * @param IdentityInterface $identity
     * @param int               $duration
     * @return bool
     */
    public function login(IdentityInterface $identity, $duration = 0)
    {
        $this->switchIdentity($identity, $duration);

        return !$this->getIsGuest();
    }

    /**
     * @throws \Exception
     */
    protected function loginByCookie()
    {
        $data = $this->getIdentityAndDurationFromCookie();
        if (isset($data['identity'], $data['duration'])) {
            $identity = $data['identity'];
            $duration = $data['duration'];
            $this->switchIdentity($identity, $duration);
        }
    }

    /**
     * @return array|null
     * @throws \Exception
     * @see   loginByCookie()
     */
    protected function getIdentityAndDurationFromCookie()
    {
        $value = request()->cookie($this->identityCookie);
        if ($value === null) {
            return null;
        }
        $data = json_decode($value, true);
        if (is_array($data) && count($data) == 3) {
            list($id, $authKey, $duration) = $data;
            /* @var $class IdentityInterface */
            $class = $this->identityClass;
            $identity = $class::findIdentity($id);
            if ($identity !== null) {
                if (!$identity instanceof IdentityInterface) {
                    throw new \Exception("$class::findIdentity() must return an object implementing IdentityInterface.");
                } elseif (!$identity->validateAuthKey($authKey)) {
                    throw new \Exception("Invalid auth key attempted for user '$id': $authKey", __METHOD__);
                } else {
                    return ['identity' => $identity, 'duration' => $duration];
                }
            }
        }
        $this->removeIdentityCookie();
        return null;
    }

    /**
     * @param bool $destroySession
     * @return bool
     */
    public function logout($destroySession = true)
    {
        $identity = $this->getIdentity();
        if ($identity !== null) {
            $this->switchIdentity(null);
            if ($destroySession) {
                request()->session()->delete($this->idParam);
            }
        }

        return $this->getIsGuest();
    }

    /**
     * @return bool
     * @see getIdentity()
     */
    public function getIsGuest()
    {
        return $this->getIdentity() === null;
    }

    protected function renewAuthStatus()
    {
        $session = request()->session();
        $id = $session->get($this->idParam);

        if ($id === null) {
            $identity = null;
        } else {
            /* @var $class IdentityInterface */
            $class = $this->identityClass;
            $identity = $class::findIdentity($id);
        }

        $this->setIdentity($identity);

        if ($this->getIsGuest()) {
            $this->loginByCookie();
        } else {
            $this->renewIdentityCookie();
        }
    }

    protected function renewIdentityCookie()
    {
        $name = $this->identityCookie;
        $value = request()->cookie($name);
        if ($value !== null) {
            $data = json_decode($value, true);
            if (is_array($data) && isset($data[2])) {
                $cookie = new Cookie();
                $cookie->name = $this->identityCookie;
                /** @var IdentityInterface $identity */
                $cookie->value = $value;
                $cookie->expire = time() + (int)$data[2];
                /** @var Response $response */
                XunshanContainer::response()->getCookies()->add($cookie);
            }
        }
    }
}
